#include "Pile.h"
#include "Tools.h"

#include "Log.h"

#include "Item.h"
#include "Item_Word.h"
#include "Item_Point.h"
#include "Item_Gene.h"

#include "Metadata.h"
#include "Metadata_Point.h"
#include "Metadata_Point2.h"
#include "Metadata_Gene.h"
#include "Metadata_Word.h"

#include <string>
#include <iostream>
#include <fstream>
#include <limits>

#include "Log.h"
#include "PS_Log.h"

Pile::~Pile() {
}

void Pile::read(const std::string& filename) {
    LOG_ENTER(INPUT,"Pile::read");
    LOG(INPUT) << "read(\"" << filename << "\")";

    Parser parser(filename);

    if (!parser.file_OK) {
        std::cerr << "Can't open " << filename << std::endl;
        LOG(ERROR) << "Can't open " << filename << std::endl;

        LOG_EXIT();
        exit(EXIT_FAILURE);
    }

    LOG(INPUT) << "opened file OK";

    // std::cout << "Pile::read.  opened file\n";
    // std::cout.flush();

    parser.next_line();
    std::string magic_number;
    parser.match_any_word(magic_number);

    LOG(INPUT) << "magic # is " << magic_number;

    // std::cout << "Pile::read.  got magic # " << line << "\n";
    // std::cout.flush();


    // Decode the magic code to get the kind of data stored in the file
    Metric_Object::Type item_type = Metric_Object::UNKNOWN;
    if      (iequals(magic_number, "point"))
        item_type = Metric_Object::POINT;
    else if (iequals(magic_number, "point2"))
        item_type = Metric_Object::POINT2;
    else if (iequals(magic_number, "word"))
        item_type = Metric_Object::WORD;
    else if (iequals(magic_number, "gene"))
        item_type = Metric_Object::GENE;

    LOG(INPUT) << "item type is " << item_type;

    // std::cout << "Pile::read.  item type " << item_type << "\n";
    // std::cout.flush();


    // parse the metadata at the top of the file
    Metadata *metadata;
    switch (item_type) {
    case Metric_Object::POINT:  metadata = new Metadata_Point();  break;
    case Metric_Object::POINT2: metadata = new Metadata_Point2(); break;
    case Metric_Object::WORD:   metadata = new Metadata_Word();   break;
    case Metric_Object::GENE:   metadata = new Metadata_Gene();   break;
    default:
        LOG(ERROR) << "Unknown magic # starting input file " << filename;
        throw std::string("Unknown magic # starting input file ")+filename;
    }

    LOG(INPUT) << "made metadata OK";

    // std::cout << "Pile::read.  made metadata\n";
    // std::cout.flush();

    metadata->parse(parser);

    LOG(INPUT) << "parsed header";

    // std::cout << "Pile::read.  parsed header\n";
    // std::cout.flush();

    // Now read the items (one per line in the file)
    std::string obj_name;
    clear();

    int index = 0;
    while (!parser.eof()) {
        parser.next_line();

        // std::cout << "Pile::read.  got line \"" << line << "\"\n";
        // std::cout.flush();

        Item *item;
        switch (item_type) {
        case Metric_Object::POINT:
        case Metric_Object::POINT2: item = new Item_Point(); break;
        case Metric_Object::WORD:   item = new Item_Word();  break;
        case Metric_Object::GENE:   item = new Item_Gene();  break;
        default:
            break; // error, but is caught in switch statement above
        }
        item->parse(parser, *metadata, obj_name);
        item->init_label(obj_name, index);

        LOG(INPUT) << "  created item=" << item;

        // this method's virtual, will be different depending on Pile type
        add(item);

        index++;
    }

    int nItems = size(); // size() is also virtual

    LOG(INPUT) << "got " << nItems << " total";
    LOG(INPUT) << "read all data OK";

    // Go through the list, to set item positions, for visualization purposes
    double dtheta = 2.0 * 3.1416 / nItems;

    double xmin =  std::numeric_limits<double>::max();
    double ymin =  xmin;
    double xmax = -xmin;
    double ymax = -ymin;

    index = 0;
    Iterator<Item*> *iter = new_iterator();
    while (iter->has_next()) {

        LOG(INPUT) << "  setting coords for index=" << index;

        Item *item = iter->get_next();

        LOG(INPUT) << "  got item=" << item;

        double x,y;
        if (item_type == Metric_Object::POINT2) { // 2D points know their positions

            LOG(INPUT) << "  it's a POINT2";

            Item_Point& p = *((Item_Point*)item);

            LOG(INPUT) << "  cast succeeded p=" << p;

            x = p[0];
            y = p[1];
        }
        else { // every other type will be on a circle
            double theta = index * dtheta;
            x = 0.5 + 0.5 * cos(theta);
            y = 0.5 + 0.5 * sin(theta);
        }
        item->x = x;
        item->y = y;

        if (x > xmax) xmax = x;
        if (y > ymax) ymax = y;
        if (x < xmin) xmin = x;
        if (y < ymin) ymin = y;

        index++;
    }
    LOG(INPUT)<< "gonna delete iter";
    Log::output->flush();

    delete iter;

    LOG(INPUT)<< "gonna set bounds";
    Log::output->flush();

//    PS_Log::ps->set_bounds(xmin,ymin, xmax,ymax);

    LOG(INPUT) << "done reading.  closing file";
    Log::output->flush();

    LOG_EXIT();
    file.close();
}

void Pile::write(std::string& file_name) {
    LOG_ENTER(OBJECT,"Pile::write(file)");

    // std::cout << "Pile::write to file\n";
    // std::cout.flush();

    std::ofstream file(file_name.c_str());
    write(file);
    file.close();

    LOG_EXIT();
}

void Pile::write(std::ostream& os) {
    LOG_ENTER(OBJECT,"Pile::write()");

    // std::cout << "Pile::write to stream\n";
    // std::cout.flush();

    Iterator<Item*> *iter = new_iterator();
    for (int i = 0; iter->has_next(); i++) {
        Item* item = iter->get_next();
        os << "item[" << i << "] ID " << item->ID
           << " \"" << item->name << "\""
           << " (x y)=(" << item->x << " " << item->y << ") \""
           << item->label << "\"\n";
    }
    delete iter;
    os.flush();

    LOG_EXIT();
}

void Pile::write_IDs(std::ostream& os) {
    LOG_ENTER(OBJECT,"Pile::write()");

    Iterator<Item*> *iter = new_iterator();
    while (iter->has_next()) {
        Item* item = iter->get_next();
        os << item->ID << " ";
    }
    delete iter;
    os.flush();

    LOG_EXIT();
}

void Pile::save_distances(std::string& filename) {
    LOG_ENTER(OBJECT,"Pile::save_distances");

    std::ofstream file(filename.c_str());

    Iterator<Item*> *iter = new_iterator();
    while (iter->has_next()) {
        Item *item = iter->get_next();

        Iterator<Item*> *jter = new_iterator();
        while (jter->has_next()) {
            Item *jtem = jter->get_next();
            double dist = item->distance(jtem);
            file << dist << " ";
        }
        delete jter;
    }
    delete iter;
    file.close();

    LOG_EXIT();
    file.close();
}

//----------------------------------------------------------------------

template class Iterator_List<Item*>;
template class Iterator_Vector<Item*>;
template class Iterator_Set<Item*>;
template class Iterator_Hash<Item*>;
